<template>
  <div class="pose-feasibility">
    <!-- 主要内容区域 -->
    <div class="main-content">
      <!-- 左侧控制面板 -->
      <div class="left-panel">
        <!-- 选择对合面 -->
        <h3 class="section-title">选择对合面</h3>
        <div class="control-section">

          <div class="input-group">
            <div class="input-row">
              <label class="input-label">文件名称</label>
              <input type="text" class="input-field" v-model="fileName" placeholder="请输入文件名称" readonly>
              <input type="file" ref="fileInput" @change="handleFileSelect" accept=".txt,.csv,.json"
                style="display: none">
              <button class="import-btn" @click="importFile">导入</button>
            </div>
            <div class="input-row">
              <label class="input-label">段体长度</label>
              <input type="text" class="input-field" v-model="segmentLength" placeholder="请输入段体长度">
            </div>
          </div>
        </div>

        <!-- 阶差验收要求 -->
        <h3 class="section-title">阶差验收要求</h3>
        <div class="control-section">

          <div class="input-group">
            <div class="input-row">
              <label class="input-label">标准</label>
              <input type="text" class="input-field" v-model="standard" placeholder="请输入标准">
              <span class="unit-label">mm</span>
            </div>
          </div>
        </div>

        <!-- 可行域不同维度的采样点数 -->
        <h3 class="section-title">可行域不同维度的采样点数</h3>
        <div class="control-section">
          <div class="input-group">
            <div class="input-row">
              <label class="input-label">X,Y</label>
              <input type="text" class="input-field" v-model="samplingPoints.xy" placeholder="请输入X,Y采样点数">
            </div>
            <div class="input-row">
              <label class="input-label">Z</label>
              <input type="text" class="input-field" v-model="samplingPoints.z" placeholder="请输入Z采样点数">
            </div>
            <div class="input-row">
              <label class="input-label">Θ</label>
              <input type="text" class="input-field" v-model="samplingPoints.theta" placeholder="请输入Θ采样点数">
            </div>
            <div class="input-row">
              <label class="input-label">Ψ,Φ</label>
              <input type="text" class="input-field" v-model="samplingPoints.psiPhi" placeholder="请输入Ψ,Φ采样点数">
            </div>
          </div>
        </div>
      </div>

      <!-- 右侧显示区域 -->
      <div class="right-panel">

        <!-- 图像显示区域 -->
        <div class="graph-section">
          <!-- 进度条 -->
          <div class="progress-section">
            <div class="progress-bar">
              <div class="progress-fill" :style="{ width: calculationProgress + '%' }"></div>
              <div class="progress-text">{{ calculationProgress }}%</div>
            </div>
          </div>
        </div>
        <div class="section-header">
          <h3 class="section-title">3D 可视化</h3>
          <div class="section-actions">
            <button class="action-btn" @click="resetView">
              <span class="btn-icon">🔄</span>
              重置视图
            </button>

          </div>
        </div>
        <div class="graph-container">
          <div ref="chartRef" class="chart-container"></div>
        </div>



      </div>
    </div>
    <!-- 日志区域 -->
    <div class="log-section">
      <div class="section-header log-section-header">
        <h3 class="section-title">实时日志</h3>
        <div class="log-controls">
          <button class="control-btn">清空</button>
          <button class="control-btn">导出</button>
        </div>
      </div>
      <div class="log-container">
        <div class="log-content">
          <div class="log-entry" v-for="(entry, index) in logEntries" :key="index" :class="entry.type">
            <span class="log-time">{{ entry.time }}</span>
            <span class="log-message">{{ entry.message }}</span>
          </div>
          <div v-if="!isPolling && calculationStore.isCalculating === false && lastErrorMessage" class="retry-bar">
            <button class="control-btn" @click="retryPolling">重试</button>
          </div>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, onMounted, onUnmounted, nextTick } from 'vue'
import * as echarts from 'echarts'
import { importFile as importFileAPI, parseFile } from '@/api/feasibility'
import toast from '@/utils/toast'
import { useCalculationStore } from '@/stores/calculation'
import { startCalculation as startCalculationAPI } from '@/api/feasibility'
import { getTaskStatus as getTaskStatusAPI } from '@/api/task'
import { getLog as getLogAPI } from '@/api/log'
import { downloadResults as downloadResultsAPI } from '@/api/feasibility'

const chartRef = ref(null)
let chartInstance = null

// 文件上传相关
const fileInput = ref(null)
const fileName = ref('')
const selectedFile = ref(null)

// 数据点存储
const dataPoints = ref([])

// 使用Pinia store
const calculationStore = useCalculationStore()

// 计算进度
const calculationProgress = ref(0)

// 输入参数
const segmentLength = ref('')
const standard = ref('')
const samplingPoints = ref({
  xy: '',
  z: '',
  theta: '',
  psiPhi: ''
})

// 日志条目
const logEntries = ref([])

// 轮询与错误状态
const isPolling = ref(false)
const lastErrorMessage = ref('')
const currentTaskId = ref(null)

// 定时器引用，用于清理
let pollingTimer = null

// 初始化所有状态
const initializeStates = () => {
  // 重置计算状态
  calculationStore.resetCalculation()

  // 重置进度
  calculationProgress.value = 0

  // 清空数据点
  dataPoints.value = []

  // 清空日志
  logEntries.value = []

  // 重置输入参数
  segmentLength.value = ''
  standard.value = ''
  samplingPoints.value = {
    xy: '',
    z: '',
    theta: '',
    psiPhi: ''
  }

  // 重置文件相关
  fileName.value = ''
  selectedFile.value = null
  if (fileInput.value) {
    fileInput.value.value = ''
  }

  // 更新图表
  updateChart()
}

// 重置视图和数据
const resetView = () => {
  // 显示确认提示
  toast.info('重置视图', '正在重置所有数据和视图...', 2000)

  // 调用状态初始化函数
  initializeStates()

  // 重置图表视图（如果有的话）
  if (chartInstance) {
    chartInstance.resize()
    // 可以添加更多图表重置逻辑
  }

  // 显示重置完成提示
  toast.success('重置完成', '视图和数据已重置，可以重新开始', 3000)
}

// 初始化图表
const initChart = () => {
  if (!chartRef.value) return

  // 配置ECharts性能优化选项
  chartInstance = echarts.init(chartRef.value, null, {
    renderer: 'canvas',
    useDirtyRect: true,
    useCoarsePointer: true,
    pointerSize: 2
  })

  const option = {
    title: {
      show: false
    },
    // 禁用可能导致性能警告的功能
    toolbox: {
      show: false
    },
    dataZoom: {
      show: false
    },
    brush: {
      show: false
    },
    grid: {
      left: '10%',
      right: '10%',
      top: '10%',
      bottom: '15%',
      containLabel: true
    },
    xAxis: {
      type: 'value',
      name: 'X轴',
      nameLocation: 'middle',
      nameGap: 30,
      min: -3,
      max: 3,
      splitNumber: 6,
      axisLine: {
        lineStyle: {
          color: '#666'
        }
      },
      axisTick: {
        show: true
      },
      axisLabel: {
        color: '#666'
      },
      splitLine: {
        show: true,
        lineStyle: {
          color: '#f0f0f0',
          type: 'dashed'
        }
      }
    },
    yAxis: {
      type: 'value',
      name: 'Y轴',
      nameLocation: 'middle',
      nameGap: 50,
      min: -1.8,
      max: 1.2,
      splitNumber: 8,
      axisLine: {
        lineStyle: {
          color: '#666'
        }
      },
      axisTick: {
        show: true
      },
      axisLabel: {
        color: '#666'
      },
      splitLine: {
        show: true,
        lineStyle: {
          color: '#f0f0f0',
          type: 'dashed'
        }
      }
    },
    series: [
      {
        name: '可行域',
        type: 'scatter',
        data: dataPoints.value,
        symbolSize: 8,
        itemStyle: {
          color: '#ff6b6b',
          shadowBlur: 10,
          shadowColor: 'rgba(255, 107, 107, 0.3)'
        },
        emphasis: {
          itemStyle: {
            color: '#ff4757',
            shadowBlur: 15,
            shadowColor: 'rgba(255, 71, 87, 0.5)'
          }
        }
      }
    ],
    legend: {
      data: ['可行域'],
      top: 10,
      right: 20,
      itemGap: 20,
      textStyle: {
        color: '#666'
      }
    },
    tooltip: {
      trigger: 'item',
      formatter: function (params) {
        return `坐标: (${params.data[0].toFixed(2)}, ${params.data[1].toFixed(2)})`
      }
    },
    // 性能优化配置
    animation: true,
    animationDuration: 1000,
    animationEasing: 'cubicOut',
    // 优化大数据量渲染
    progressive: 1000,
    progressiveThreshold: 3000,
    // 优化交互性能
    silent: false,
    // 禁用缩放和拖拽功能以避免性能警告
    zoom: false,
    roam: false,
    // 禁用选择功能
    select: {
      disabled: true
    },
    // 优化渲染性能
    large: false,
    largeThreshold: 2000
  }

  chartInstance.setOption(option)
}

// 组件挂载后初始化图表
onMounted(() => {
  nextTick(() => {
    initChart()
  })

  // 重置计算状态，确保页面加载时状态正确
  calculationStore.resetCalculation()
})

// 组件卸载时清理定时器
onUnmounted(() => {
  if (pollingTimer) {
    clearTimeout(pollingTimer)
    pollingTimer = null
  }
})
// 处理文件选择
const handleFileSelect = async (event) => {
  const file = event.target.files[0]
  if (!file) return

  selectedFile.value = file
  fileName.value = file.name

  // 选择文件后直接上传
  await uploadFile(file)
}

// 上传文件
const uploadFile = async (file) => {
  try {
    // 创建FormData对象
    const formData = new FormData()
    formData.append('file', file)

    // 第一步：上传文件
    const uploadResponse = await importFileAPI(formData)
    if (uploadResponse && uploadResponse.filename) {
      // 显示上传成功提示
      toast.success('文件上传成功', `文件 ${uploadResponse.filename} 已成功上传`, 2000)

      // 第二步：解析文件
      try {
        const parseResponse = await parseFile(uploadResponse.filename)
        if (parseResponse && Array.isArray(parseResponse)) {
          // 显示解析成功提示
          toast.success('文件解析成功', `文件 ${uploadResponse.filename} 解析完成，共 ${parseResponse.length} 个数据点`, 3000)

          // 转换数据格式为ECharts需要的格式
          const points = parseResponse.map(item => item.pos)
          dataPoints.value = points
          updateChart()
        } else {
          throw new Error('解析响应数据格式错误')
        }
      } catch (parseError) {
        toast.error('文件解析失败', parseError.message || '文件解析出错', 5000)

      }
    } else {
      throw new Error('上传响应格式错误')
    }
  } catch (error) {
    // 显示错误提示
    toast.error('文件上传失败', error.message || '请检查文件格式或网络连接', 5000)

  }
}

// 导入文件（点击按钮触发文件选择）
const importFile = () => {
  fileInput.value.click()
}

// 更新图表
const updateChart = () => {
  if (chartInstance) {
    const option = chartInstance.getOption()
    option.series[0].data = dataPoints.value
    chartInstance.setOption(option)
  }
}
// 验证输入数据
const validateInputs = () => {
  const errors = []

  // 检查文件是否导入成功
  if (!selectedFile.value || !dataPoints.value || dataPoints.value.length === 0) {
    errors.push('请先导入数据文件')
  }

  // 检查段体长度
  if (!segmentLength.value.trim()) {
    errors.push('请输入段体长度')
  }

  // 检查标准
  if (!standard.value.trim()) {
    errors.push('请输入阶差验收标准')
  }

  // 检查采样点数
  if (!samplingPoints.value.xy.trim()) {
    errors.push('请输入X,Y采样点数')
  }
  if (!samplingPoints.value.z.trim()) {
    errors.push('请输入Z采样点数')
  }
  if (!samplingPoints.value.theta.trim()) {
    errors.push('请输入Θ采样点数')
  }
  if (!samplingPoints.value.psiPhi.trim()) {
    errors.push('请输入Ψ,Φ采样点数')
  }

  return errors
}

// 轮询任务状态
const pollTaskStatus = async (taskId) => {
  const maxAttempts = 300 // 最多轮询10分钟
  let attempts = 0
  let pollInterval = 1000 // 初始1秒
  let consecutiveNoProgress = 0 // 连续无进度次数
  isPolling.value = true

  const poll = async () => {
    try {
      attempts++
      const res = await getTaskStatusAPI(taskId)

      // 获取任务日志
      try {
        const logResponse = await getLogAPI(taskId)
        if (logResponse && logResponse.logs) {
          // 解析服务器返回的日志字符串数组
          logResponse.logs.forEach(logString => {
            // 解析日志格式: "2025-10-15 21:55:49,797 - INFO - message"
            const logMatch = logString.match(/^(\d{4}-\d{2}-\d{2} \d{2}:\d{2}:\d{2},\d{3}) - (\w+) - (.+)$/)
            if (logMatch) {
              const [, timestamp, level, message] = logMatch
              // 转换时间格式
              const timeStr = timestamp.replace(',', '.').replace(' ', 'T') + 'Z'
              const logTime = new Date(timeStr).toLocaleTimeString()

              // 映射日志级别
              let logType = 'info'
              if (level === 'ERROR') logType = 'error'
              else if (level === 'WARNING') logType = 'warning'
              else if (level === 'SUCCESS') logType = 'success'

              // 检查是否已经存在相同的日志条目，避免重复添加
              const logExists = logEntries.value.some(entry =>
                entry.time === logTime && entry.message === message.trim()
              )

              if (!logExists) {
                logEntries.value.push({
                  time: logTime,
                  message: message.trim(),
                  type: logType
                })
              }
            } else {
              // 如果解析失败，直接添加原始字符串
              const logExists = logEntries.value.some(entry =>
                entry.message === logString.trim()
              )

              if (!logExists) {
                logEntries.value.push({
                  time: new Date().toLocaleTimeString(),
                  message: logString.trim(),
                  type: 'info'
                })
              }
            }
          })
        }
      } catch (logError) {
        // 如果获取日志失败，不影响主流程
        if (attempts === 1) { // 只在第一次失败时显示警告
          logEntries.value.push({
            time: new Date().toLocaleTimeString(),
            message: '获取任务日志失败',
            type: 'warning'
          })
        }
      }

      // 更新进度条
      if (res.progress !== undefined) {
        const previousProgress = calculationProgress.value
        calculationProgress.value = Math.round(res.progress)

        // 检查进度是否有变化
        if (calculationProgress.value > previousProgress) {
          consecutiveNoProgress = 0
          // 进度有变化，保持较短轮询间隔
          pollInterval = 1000
        } else {
          consecutiveNoProgress++
          // 进度无变化，逐渐增加轮询间隔
          if (consecutiveNoProgress > 3) {
            pollInterval = Math.min(pollInterval * 1.5, 10000) // 最大10秒
          }
        }

        // 如果进度达到100%，标记为完成
        if (res.progress >= 100) {
          calculationStore.completeCalculation()
          toast.success('计算完成', '位姿可行域推算已完成，可以下载结果', 3000)
          isPolling.value = false
          return // 停止轮询
        }
      }
      if (res.status === 'completed') {
        // 任务完成
        calculationStore.completeCalculation()

        // 更新日志
        logEntries.value.push({
          time: new Date().toLocaleTimeString(),
          message: '计算完成，可以下载结果',
          type: 'success'
        })

        toast.success('计算完成', '位姿可行域推算已完成，可以下载结果', 3000)

        // 存储计算结果
        if (res.result) {
          // 处理计算结果数据
        }
        isPolling.value = false

      } else if (res.status === 'failed') {
        // 任务失败
        throw new Error(res.error || '计算任务失败')

      } else if (res.status === 'pending') {
        // 任务等待中 - 使用较长间隔
        if (attempts < maxAttempts) {
          pollingTimer = setTimeout(poll, 3000) // 3秒后再次检查
        } else {
          throw new Error('计算超时，请稍后重试')
        }
      } else if (res.status === 'running') {
        // 任务运行中
        if (attempts < maxAttempts) {
          // 如果进度有变化，更新日志
          if (res.progress > 0 && res.progress % 10 === 0) {
            logEntries.value.push({
              time: new Date().toLocaleTimeString(),
              message: `计算进行中... ${calculationProgress.value}%`,
              type: 'info'
            })
          }

          // 如果进度未达到100%，继续轮询
          if (res.progress < 100) {
            pollingTimer = setTimeout(poll, pollInterval) // 使用动态间隔
          }
          // 如果进度达到100%，上面的进度检查逻辑会处理完成状态
        } else {
          throw new Error('计算超时，请稍后重试')
        }
      } else {
        // 未知状态 - 使用较长间隔
        if (attempts < maxAttempts) {
          pollingTimer = setTimeout(poll, 5000)
        } else {
          throw new Error('任务状态异常，请稍后重试')
        }
      }

    } catch (error) {
      // 停止轮询与记录错误
      if (pollingTimer) {
        clearTimeout(pollingTimer)
        pollingTimer = null
      }
      isPolling.value = false
      lastErrorMessage.value = (error && (error.stack || error.message)) ? (error.stack || error.message) : '未知错误'
      calculationStore.resetCalculation()

      // 写入详细错误日志（包含堆栈）
      logEntries.value.push({
        time: new Date().toLocaleTimeString(),
        message: `任务状态检查失败: ${error.message}`,
        type: 'error'
      })
      if (error && error.stack) {
        logEntries.value.push({
          time: new Date().toLocaleTimeString(),
          message: error.stack,
          type: 'error'
        })
      }

      toast.error('计算失败', '任务异常已停止轮询，可点击重试', 5000)
    }
  }

  // 开始轮询
  logEntries.value.push({
    time: new Date().toLocaleTimeString(),
    message: `开始监控任务状态，任务ID: ${taskId}`,
    type: 'info'
  })
  poll()
}

// 开始计算
const startCalculate = async () => {
  // 验证输入数据
  const validationErrors = validateInputs()

  if (validationErrors.length > 0) {
    // 显示错误提示
    const errorMessage = validationErrors.join('；')
    toast.error('数据验证失败', errorMessage, 500)
    // 重置计算状态，因为验证失败
    calculationStore.resetCalculation()
    return
  }

  // 重置进度
  calculationProgress.value = 0

  try {
    // 所有验证通过，开始计算
    const res = await startCalculationAPI({
      file: true,
      file_name: fileName.value,
      h_M: parseFloat(standard.value),
      component_length: parseFloat(segmentLength.value),
      Node_pan_xy_num: parseInt(samplingPoints.value.xy),
      Node_pan_z_num: parseInt(samplingPoints.value.z),
      Node_rot_t_num: parseInt(samplingPoints.value.theta),
      Node_rot_pp_num: parseInt(samplingPoints.value.psiPhi)
    })
    // 更新日志
    logEntries.value.push({
      time: new Date().toLocaleTimeString(),
      message: `计算任务已创建，任务ID: ${res.task_id}`,
      type: 'info'
    })


    // 开始轮询任务状态
    currentTaskId.value = res.task_id
    await pollTaskStatus(res.task_id)
  } catch (error) {
    // 重置计算状态
    calculationStore.resetCalculation()

    // 更新日志
    logEntries.value.push({
      time: new Date().toLocaleTimeString(),
      message: `计算失败: ${error.message}`,
      type: 'error'
    })

    toast.error('计算失败', error.message || '请检查网络连接或参数设置', 5000)
  }
}
// 重试轮询
const retryPolling = () => {
  if (currentTaskId.value) {
    toast.info('重试', '正在重新检查任务状态...', 2000)
    pollTaskStatus(currentTaskId.value)
  } else {
    toast.error('无法重试', '没有可用的任务ID，请重新开始计算', 4000)
  }
}
// 下载计算结果
const downloadResults = async () => {
  try {
    // 检查计算是否完成
    if (!calculationStore.calculationCompleted) {
      toast.error('下载失败', '请先完成计算任务', 3000)
      return
    }

    // 显示下载开始提示
    toast.info('开始下载', '正在准备下载 .npy 文件...', 2000)

    // 调用API获取计算结果（传入 frame 参数，如果有的话）
    // 优先从采样点或段体长度等推断帧数；若页面无帧数输入，这里可按后端默认处理
    const frameParam = undefined
    const res = await downloadResultsAPI(frameParam)
    console.log('下载API响应:', res)

    // 生成 .npy 文件名
    const currentTime = new Date().toISOString().replace(/[:.]/g, '-')
    const fileName = `pose-feasibility-results-${currentTime}.npy`

    // 检查响应是否为 Blob 对象（.npy 文件）
    if (res instanceof Blob) {
      // 直接下载 .npy 文件
      const url = URL.createObjectURL(res)
      const link = document.createElement('a')
      link.href = url
      link.download = fileName
      link.style.display = 'none'
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      URL.revokeObjectURL(url)

      // 显示下载成功提示
      toast.success('下载成功', `文件 ${fileName} 已开始下载`, 3000)

      // 更新日志
      logEntries.value.push({
        time: new Date().toLocaleTimeString(),
        message: `计算结果已下载: ${fileName}`,
        type: 'success'
      })

      // 下载成功后初始化所有状态
      initializeStates()
      return
    }

    // 如果响应有 data 字段且是 Blob
    if (res && res.data && res.data instanceof Blob) {
      const url = URL.createObjectURL(res.data)
      const link = document.createElement('a')
      link.href = url
      link.download = fileName
      link.style.display = 'none'
      document.body.appendChild(link)
      link.click()
      document.body.removeChild(link)
      URL.revokeObjectURL(url)

      toast.success('下载成功', `文件 ${fileName} 已开始下载`, 3000)

      // 下载成功后初始化所有状态
      initializeStates()
    }
  } catch (error) {
    console.error('下载失败:', error)
    // 显示错误提示
    toast.error('下载失败', error.message || '下载过程中出现错误', 5000)
  }
}
// 暴露方法给父组件
defineExpose({
  startCalculate,
  downloadResults
})

</script>

<style scoped>
/* 性能优化：启用硬件加速和优化滚动 */
* {
  -webkit-overflow-scrolling: touch;
  scroll-behavior: smooth;
}

.pose-feasibility {
  height: 100%;
  display: flex;
  flex-direction: column;
  /* 启用硬件加速 */
  transform: translateZ(0);
  will-change: transform;
}

.progress-section {
  margin-bottom: 20px;
  margin-top: 10px
}

.progress-bar {
  width: 100%;
  height: 12px;
  background: linear-gradient(90deg, #2d2d2d 0%, #3a3a3a 50%, #2d2d2d 100%);
  border-radius: 6px;
  position: relative;
  overflow: hidden;
  box-shadow:
    inset 0 2px 4px rgba(0, 0, 0, 0.3),
    0 2px 8px rgba(0, 0, 0, 0.2);
  border: 1px solid #444;
  margin-top: 30px
}

.progress-bar::before {
  content: '';
  position: absolute;
  top: 0;
  left: 0;
  right: 0;
  bottom: 0;
  background: linear-gradient(90deg,
      transparent 0%,
      rgba(255, 255, 255, 0.1) 50%,
      transparent 100%);
  animation: shimmer 2s infinite;
}

.progress-fill {
  height: 100%;
  background: #485FD4;
  border-radius: 6px;
  transition: width 0.8s cubic-bezier(0.4, 0, 0.2, 1);
  position: relative;
  overflow: hidden;
  box-shadow:
    0 0 20px rgba(76, 175, 80, 0.6),
    inset 0 1px 0 rgba(255, 255, 255, 0.3);
}

.progress-fill::before {
  content: '';
  position: absolute;
  top: 0;
  left: -100%;
  width: 100%;
  height: 100%;
  background: linear-gradient(90deg,
      transparent 0%,
      rgba(255, 255, 255, 0.4) 50%,
      transparent 100%);
  animation: progress-shine 1.5s infinite;
}

.progress-text {
  position: absolute;
  top: 50%;
  left: 50%;
  transform: translate(-50%, -50%);
  color: #ffffff;
  font-weight: 700;
  font-size: 14px;
  text-shadow: 0 1px 3px rgba(0, 0, 0, 0.8);
  z-index: 2;
}

@keyframes shimmer {
  0% {
    transform: translateX(-100%);
  }

  100% {
    transform: translateX(100%);
  }
}

@keyframes progress-shine {
  0% {
    left: -100%;
  }

  100% {
    left: 100%;
  }
}

.main-content {
  flex: 1;
  display: flex;
  gap: 40px;
  /* padding-top: 20px; */
}

.left-panel {
  width: 400px;
  display: flex;
  flex-direction: column;
  /* gap: 20px; */
  justify-content: space-between;
}

.right-panel {
  flex: 1;
  display: flex;
  flex-direction: column;
  gap: 20px;
}

.control-section {
  background-color: #2a2a2a;
  border-radius: 8px;
  padding: 20px;
  box-shadow: 0 2px 4px rgba(0, 0, 0, 0.1);
  margin-bottom: 20px;
}

.section-title {
  font-size: 16px;
  font-weight: 600;
  color: #ffffff;
  margin: 0 0 15px 0;
}

.input-group {
  display: flex;
  flex-direction: column;
  gap: 15px;
}

.input-row {
  display: flex;
  align-items: center;
  gap: 10px;
}

.input-label {
  min-width: 80px;
  font-size: 14px;
  color: #e0e0e0;
  font-weight: 500;
}

.input-field {
  flex: 1;
  height: 36px;
  padding: 0 12px;
  border: 1px solid #555;
  border-radius: 4px;
  font-size: 14px;
  color: #ffffff;
  background-color: rgba(255, 255, 255, 0.1);
  transition: border-color 0.3s ease;
}

.input-field:focus {
  outline: none;
  border-color: #4a90e2;
  box-shadow: 0 0 0 2px rgba(74, 144, 226, 0.2);
}

.input-field::placeholder {
  color: #999999;
}

.import-btn {
  height: 36px;
  width: 80px;
  /* padding: 0 16px; */
  background: #485FD4;
  color: #ffffff;
  border: none;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: background-color 0.3s ease;
}

.import-btn:hover:not(:disabled) {
  background-color: #357abd;
}

.import-btn:disabled {
  background-color: #6c757d;
  cursor: not-allowed;
  opacity: 0.6;
}

.unit-label {
  font-size: 14px;
  color: #e0e0e0;
  font-weight: 500;
}

.graph-section,
.log-section {
  background: transparent;
  border-radius: 12px;
  display: flex;
  flex-direction: column;
  /* padding-bottom: 30px; */
}

.log-section {
  margin-top: 20px;
}

.section-header {
  display: flex;
  justify-content: space-between;
  align-items: center;
  /* margin-bottom: 20px; */
}

.section-actions {
  display: flex;
  gap: 8px;
}

.action-btn {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 16px;
  background: #485FD4;
  color: #ffffff;
  border: none;
  border-radius: 6px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
  box-shadow: 0 2px 8px rgba(74, 144, 226, 0.3);
}

.action-btn:hover {
  transform: translateY(-1px);
  box-shadow: 0 4px 12px rgba(74, 144, 226, 0.4);
}

.btn-icon {
  font-size: 14px;
}

.graph-container {
  height: calc(100vh - 320px);
  margin-top: 0;
}

.chart-container {
  width: 100%;
  height: 100%;
  background: linear-gradient(135deg, #f8f9fa 0%, #ffffff 100%);
  border: 2px solid #e9ecef;
  border-radius: 8px;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06);
  /* 性能优化：启用硬件加速和优化事件处理 */
  transform: translateZ(0);
  will-change: transform;
  /* 优化滚动性能 */
  overflow: hidden;
  /* 禁用文本选择以提高性能 */
  user-select: none;
  -webkit-user-select: none;
  -moz-user-select: none;
  -ms-user-select: none;
}


.axis {
  position: absolute;
}

.axis.x-axis {
  bottom: 20px;
  left: 20px;
  right: 20px;
  height: 2px;
}

.axis.y-axis {
  left: 20px;
  top: 20px;
  bottom: 20px;
  width: 2px;
}

.axis-line {
  background: linear-gradient(90deg, #6c757d 0%, #495057 50%, #6c757d 100%);
  border-radius: 1px;
}

.x-axis .axis-line {
  width: 100%;
  height: 2px;
}

.y-axis .axis-line {
  width: 2px;
  height: 100%;
}

.axis-label {
  position: absolute;
  font-size: 14px;
  font-weight: 600;
  color: #495057;
  background: #ffffff;
  padding: 4px 8px;
  border-radius: 4px;
  box-shadow: 0 1px 3px rgba(0, 0, 0, 0.1);
}

.x-axis .axis-label {
  top: -35px;
  left: 50%;
  transform: translateX(-50%);
}

.y-axis .axis-label {
  left: -50px;
  top: 50%;
  transform: translateY(-50%) rotate(-90deg);
}

.axis-ticks {
  position: absolute;
  display: flex;
  justify-content: space-between;
  align-items: center;
}

.x-axis .axis-ticks {
  top: 8px;
  left: 0;
  right: 0;
}

.y-axis .axis-ticks {
  left: 8px;
  top: 0;
  bottom: 0;
  flex-direction: column;
}

.tick {
  font-size: 11px;
  color: #6c757d;
  font-weight: 500;
  background: #ffffff;
  padding: 2px 4px;
  border-radius: 2px;
  box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}

.grid-lines {
  position: absolute;
  top: 30px;
  left: 40px;
  right: 40px;
  bottom: 30px;
}

.grid-line {
  position: absolute;
  background: rgba(108, 117, 125, 0.1);
  border-radius: 0.5px;
}

.grid-line.vertical {
  width: 1px;
  height: 100%;
}

.grid-line.horizontal {
  height: 1px;
  width: 100%;
}

.data-points {
  position: absolute;
  top: 30px;
  left: 40px;
  right: 40px;
  bottom: 30px;
}

.data-point {
  position: absolute;
  width: 6px;
  height: 6px;
  background: radial-gradient(circle, #ff6b6b 0%, #ee5a52 100%);
  border-radius: 50%;
  box-shadow:
    0 2px 4px rgba(255, 107, 107, 0.3),
    0 0 0 2px rgba(255, 107, 107, 0.1);
  animation: dataPointPulse 2s infinite;
}

@keyframes dataPointPulse {

  0%,
  100% {
    transform: scale(1);
    opacity: 1;
  }

  50% {
    transform: scale(1.2);
    opacity: 0.8;
  }
}

.legend {
  position: absolute;
  top: 20px;
  right: 20px;
  background: rgba(255, 255, 255, 0.95);
  padding: 12px;
  border-radius: 8px;
  box-shadow: 0 2px 8px rgba(0, 0, 0, 0.1);
  backdrop-filter: blur(10px);
}

.legend-item {
  display: flex;
  align-items: center;
  gap: 8px;
  margin-bottom: 6px;
  font-size: 12px;
  color: #495057;
  font-weight: 500;
}

.legend-item:last-child {
  margin-bottom: 0;
}

.legend-color {
  width: 12px;
  height: 12px;
  border-radius: 50%;
  background: radial-gradient(circle, #ff6b6b 0%, #ee5a52 100%);
  box-shadow: 0 1px 3px rgba(255, 107, 107, 0.3);
}

.legend-color.boundary {
  background: linear-gradient(45deg, #4a90e2 0%, #357abd 100%);
  box-shadow: 0 1px 3px rgba(74, 144, 226, 0.3);
}

.log-controls {
  display: flex;
  gap: 8px;
}

.control-btn {
  padding: 6px 12px;
  background: #f8f9fa;
  color: #6c757d;
  border: 1px solid #dee2e6;
  border-radius: 4px;
  font-size: 12px;
  font-weight: 500;
  cursor: pointer;
  transition: all 0.3s ease;
}

.control-btn:hover {
  background: #e9ecef;
  color: #495057;
  border-color: #adb5bd;
}

.log-container {
  height: 200px;
  margin-top: 0;
  flex-shrink: 0;
  padding-right: 30px;
  padding-bottom: 30px;
  margin-bottom: 30px;
}

.log-content {
  width: 100%;
  height: 100%;
  background: #2d2d2d;
  border-radius: 8px;
  padding: 16px;
  overflow-y: auto;
  font-family: 'Monaco', 'Menlo', 'Ubuntu Mono', monospace;
  box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.2);
}

.retry-bar {
  padding-top: 12px;
}

.log-section-header {
  margin-bottom: 20px;
}

.log-entry {
  display: flex;
  gap: 12px;
  margin-bottom: 8px;
  font-size: 13px;
  line-height: 1.4;
}

.log-entry:last-child {
  margin-bottom: 0;
}

.log-time {
  color: #6c757d;
  font-weight: 500;
  min-width: 70px;
  flex-shrink: 0;
}

.log-message {
  color: #ffffff;
  flex: 1;
}

.log-entry.info .log-message {
  color: #17a2b8;
}

.log-entry.success .log-message {
  color: #28a745;
}

.log-entry.warning .log-message {
  color: #ffc107;
}

.log-entry.error .log-message {
  color: #dc3545;
}

/* 滚动条样式 */
.log-content::-webkit-scrollbar {
  width: 6px;
}

.log-content::-webkit-scrollbar-track {
  background: #2d2d2d;
  border-radius: 3px;
}

.log-content::-webkit-scrollbar-thumb {
  background: #495057;
  border-radius: 3px;
}

.log-content::-webkit-scrollbar-thumb:hover {
  background: #6c757d;
}
</style>